// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/android/scoped_java_ref.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace android {

    namespace {
        int g_local_refs = 0;
        int g_global_refs = 0;

        const JNINativeInterface* g_previous_functions;

        jobject NewGlobalRef(JNIEnv* env, jobject obj)
        {
            ++g_global_refs;
            return g_previous_functions->NewGlobalRef(env, obj);
        }

        void DeleteGlobalRef(JNIEnv* env, jobject obj)
        {
            --g_global_refs;
            return g_previous_functions->DeleteGlobalRef(env, obj);
        }

        jobject NewLocalRef(JNIEnv* env, jobject obj)
        {
            ++g_local_refs;
            return g_previous_functions->NewLocalRef(env, obj);
        }

        void DeleteLocalRef(JNIEnv* env, jobject obj)
        {
            --g_local_refs;
            return g_previous_functions->DeleteLocalRef(env, obj);
        }
    } // namespace

    class ScopedJavaRefTest : public testing::Test {
    protected:
        void SetUp() override
        {
            g_local_refs = 0;
            g_global_refs = 0;
            JNIEnv* env = AttachCurrentThread();
            g_previous_functions = env->functions;
            hooked_functions = *g_previous_functions;
            env->functions = &hooked_functions;
            // We inject our own functions in JNINativeInterface so we can keep track
            // of the reference counting ourselves.
            hooked_functions.NewGlobalRef = &NewGlobalRef;
            hooked_functions.DeleteGlobalRef = &DeleteGlobalRef;
            hooked_functions.NewLocalRef = &NewLocalRef;
            hooked_functions.DeleteLocalRef = &DeleteLocalRef;
        }

        void TearDown() override
        {
            JNIEnv* env = AttachCurrentThread();
            env->functions = g_previous_functions;
        }
        // From JellyBean release, the instance of this struct provided in JNIEnv is
        // read-only, so we deep copy it to allow individual functions to be hooked.
        JNINativeInterface hooked_functions;
    };

    // The main purpose of this is testing the various conversions compile.
    TEST_F(ScopedJavaRefTest, Conversions)
    {
        JNIEnv* env = AttachCurrentThread();
        ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string");
        ScopedJavaGlobalRef<jstring> global(str);
        {
            ScopedJavaGlobalRef<jobject> global_obj(str);
            ScopedJavaLocalRef<jobject> local_obj(global);
            const JavaRef<jobject>& obj_ref1(str);
            const JavaRef<jobject>& obj_ref2(global);
            EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj()));
            EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj()));
        }
        global.Reset(str);
        const JavaRef<jstring>& str_ref = str;
        EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref));
        str.Reset();
    }

    TEST_F(ScopedJavaRefTest, RefCounts)
    {
        JNIEnv* env = AttachCurrentThread();
        ScopedJavaLocalRef<jstring> str;
        // The ConvertJavaStringToUTF8 below creates a new string that would normally
        // return a local ref. We simulate that by starting the g_local_refs count at
        // 1.
        g_local_refs = 1;
        str.Reset(ConvertUTF8ToJavaString(env, "string"));
        EXPECT_EQ(1, g_local_refs);
        EXPECT_EQ(0, g_global_refs);
        {
            ScopedJavaGlobalRef<jstring> global_str(str);
            ScopedJavaGlobalRef<jobject> global_obj(global_str);
            EXPECT_EQ(1, g_local_refs);
            EXPECT_EQ(2, g_global_refs);

            ScopedJavaLocalRef<jstring> str2(env, str.Release());
            EXPECT_EQ(1, g_local_refs);
            {
                ScopedJavaLocalRef<jstring> str3(str2);
                EXPECT_EQ(2, g_local_refs);
            }
            EXPECT_EQ(1, g_local_refs);
            str2.Reset();
            EXPECT_EQ(0, g_local_refs);
            global_str.Reset();
            EXPECT_EQ(1, g_global_refs);
            ScopedJavaGlobalRef<jobject> global_obj2(global_obj);
            EXPECT_EQ(2, g_global_refs);
        }

        EXPECT_EQ(0, g_local_refs);
        EXPECT_EQ(0, g_global_refs);
    }

} // namespace android
} // namespace base
